{
 "nbformat_minor": 0, 
 "nbformat": 4, 
 "cells": [
  {
   "source": [
    "# Softmax exercise\n", 
    "\n", 
    "*Complete and hand in this completed worksheet (including its outputs and any supporting code outside of the worksheet) with your assignment submission. For more details see the [assignments page](http://vision.stanford.edu/teaching/cs231n/assignments.html) on the course website.*\n", 
    "\n", 
    "This exercise is analogous to the SVM exercise. You will:\n", 
    "\n", 
    "- implement a fully-vectorized **loss function** for the Softmax classifier\n", 
    "- implement the fully-vectorized expression for its **analytic gradient**\n", 
    "- **check your implementation** with numerical gradient\n", 
    "- use a validation set to **tune the learning rate and regularization** strength\n", 
    "- **optimize** the loss function with **SGD**\n", 
    "- **visualize** the final learned weights\n"
   ], 
   "cell_type": "markdown", 
   "metadata": {}
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "import random\n", 
    "import numpy as np\n", 
    "from cs231n.data_utils import load_CIFAR10\n", 
    "import matplotlib.pyplot as plt\n", 
    "\n", 
    "from __future__ import print_function\n", 
    "\n", 
    "%matplotlib inline\n", 
    "plt.rcParams['figure.figsize'] = (10.0, 8.0) # set default size of plots\n", 
    "plt.rcParams['image.interpolation'] = 'nearest'\n", 
    "plt.rcParams['image.cmap'] = 'gray'\n", 
    "\n", 
    "# for auto-reloading extenrnal modules\n", 
    "# see http://stackoverflow.com/questions/1907993/autoreload-of-modules-in-ipython\n", 
    "%load_ext autoreload\n", 
    "%autoreload 2"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "def get_CIFAR10_data(num_training=49000, num_validation=1000, num_test=1000, num_dev=500):\n", 
    "    \"\"\"\n", 
    "    Load the CIFAR-10 dataset from disk and perform preprocessing to prepare\n", 
    "    it for the linear classifier. These are the same steps as we used for the\n", 
    "    SVM, but condensed to a single function.  \n", 
    "    \"\"\"\n", 
    "    # Load the raw CIFAR-10 data\n", 
    "    cifar10_dir = 'cs231n/datasets/cifar-10-batches-py'\n", 
    "    X_train, y_train, X_test, y_test = load_CIFAR10(cifar10_dir)\n", 
    "    \n", 
    "    # subsample the data\n", 
    "    mask = list(range(num_training, num_training + num_validation))\n", 
    "    X_val = X_train[mask]\n", 
    "    y_val = y_train[mask]\n", 
    "    mask = list(range(num_training))\n", 
    "    X_train = X_train[mask]\n", 
    "    y_train = y_train[mask]\n", 
    "    mask = list(range(num_test))\n", 
    "    X_test = X_test[mask]\n", 
    "    y_test = y_test[mask]\n", 
    "    mask = np.random.choice(num_training, num_dev, replace=False)\n", 
    "    X_dev = X_train[mask]\n", 
    "    y_dev = y_train[mask]\n", 
    "    \n", 
    "    # Preprocessing: reshape the image data into rows\n", 
    "    X_train = np.reshape(X_train, (X_train.shape[0], -1))\n", 
    "    X_val = np.reshape(X_val, (X_val.shape[0], -1))\n", 
    "    X_test = np.reshape(X_test, (X_test.shape[0], -1))\n", 
    "    X_dev = np.reshape(X_dev, (X_dev.shape[0], -1))\n", 
    "    \n", 
    "    # Normalize the data: subtract the mean image\n", 
    "    mean_image = np.mean(X_train, axis = 0)\n", 
    "    X_train -= mean_image\n", 
    "    X_val -= mean_image\n", 
    "    X_test -= mean_image\n", 
    "    X_dev -= mean_image\n", 
    "    \n", 
    "    # add bias dimension and transform into columns\n", 
    "    X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))])\n", 
    "    X_val = np.hstack([X_val, np.ones((X_val.shape[0], 1))])\n", 
    "    X_test = np.hstack([X_test, np.ones((X_test.shape[0], 1))])\n", 
    "    X_dev = np.hstack([X_dev, np.ones((X_dev.shape[0], 1))])\n", 
    "    \n", 
    "    return X_train, y_train, X_val, y_val, X_test, y_test, X_dev, y_dev\n", 
    "\n", 
    "\n", 
    "# Invoke the above function to get our data.\n", 
    "X_train, y_train, X_val, y_val, X_test, y_test, X_dev, y_dev = get_CIFAR10_data()\n", 
    "print('Train data shape: ', X_train.shape)\n", 
    "print('Train labels shape: ', y_train.shape)\n", 
    "print('Validation data shape: ', X_val.shape)\n", 
    "print('Validation labels shape: ', y_val.shape)\n", 
    "print('Test data shape: ', X_test.shape)\n", 
    "print('Test labels shape: ', y_test.shape)\n", 
    "print('dev data shape: ', X_dev.shape)\n", 
    "print('dev labels shape: ', y_dev.shape)"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "source": [
    "## Softmax Classifier\n", 
    "\n", 
    "Your code for this section will all be written inside **cs231n/classifiers/softmax.py**. \n"
   ], 
   "cell_type": "markdown", 
   "metadata": {}
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# First implement the naive softmax loss function with nested loops.\n", 
    "# Open the file cs231n/classifiers/softmax.py and implement the\n", 
    "# softmax_loss_naive function.\n", 
    "\n", 
    "from cs231n.classifiers.softmax import softmax_loss_naive\n", 
    "import time\n", 
    "\n", 
    "# Generate a random softmax weight matrix and use it to compute the loss.\n", 
    "W = np.random.randn(3073, 10) * 0.0001\n", 
    "loss, grad = softmax_loss_naive(W, X_dev, y_dev, 0.0)\n", 
    "\n", 
    "# As a rough sanity check, our loss should be something close to -log(0.1).\n", 
    "print('loss: %f' % loss)\n", 
    "print('sanity check: %f' % (-np.log(0.1)))"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "source": [
    "## Inline Question 1:\n", 
    "Why do we expect our loss to be close to -log(0.1)? Explain briefly.**\n", 
    "\n", 
    "**Your answer:** *Fill this in*\n"
   ], 
   "cell_type": "markdown", 
   "metadata": {}
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# Complete the implementation of softmax_loss_naive and implement a (naive)\n", 
    "# version of the gradient that uses nested loops.\n", 
    "loss, grad = softmax_loss_naive(W, X_dev, y_dev, 0.0)\n", 
    "\n", 
    "# As we did for the SVM, use numeric gradient checking as a debugging tool.\n", 
    "# The numeric gradient should be close to the analytic gradient.\n", 
    "from cs231n.gradient_check import grad_check_sparse\n", 
    "f = lambda w: softmax_loss_naive(w, X_dev, y_dev, 0.0)[0]\n", 
    "grad_numerical = grad_check_sparse(f, W, grad, 10)\n", 
    "\n", 
    "# similar to SVM case, do another gradient check with regularization\n", 
    "loss, grad = softmax_loss_naive(W, X_dev, y_dev, 5e1)\n", 
    "f = lambda w: softmax_loss_naive(w, X_dev, y_dev, 5e1)[0]\n", 
    "grad_numerical = grad_check_sparse(f, W, grad, 10)"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# Now that we have a naive implementation of the softmax loss function and its gradient,\n", 
    "# implement a vectorized version in softmax_loss_vectorized.\n", 
    "# The two versions should compute the same results, but the vectorized version should be\n", 
    "# much faster.\n", 
    "tic = time.time()\n", 
    "loss_naive, grad_naive = softmax_loss_naive(W, X_dev, y_dev, 0.000005)\n", 
    "toc = time.time()\n", 
    "print('naive loss: %e computed in %fs' % (loss_naive, toc - tic))\n", 
    "\n", 
    "from cs231n.classifiers.softmax import softmax_loss_vectorized\n", 
    "tic = time.time()\n", 
    "loss_vectorized, grad_vectorized = softmax_loss_vectorized(W, X_dev, y_dev, 0.000005)\n", 
    "toc = time.time()\n", 
    "print('vectorized loss: %e computed in %fs' % (loss_vectorized, toc - tic))\n", 
    "\n", 
    "# As we did for the SVM, we use the Frobenius norm to compare the two versions\n", 
    "# of the gradient.\n", 
    "grad_difference = np.linalg.norm(grad_naive - grad_vectorized, ord='fro')\n", 
    "print('Loss difference: %f' % np.abs(loss_naive - loss_vectorized))\n", 
    "print('Gradient difference: %f' % grad_difference)"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# Use the validation set to tune hyperparameters (regularization strength and\n", 
    "# learning rate). You should experiment with different ranges for the learning\n", 
    "# rates and regularization strengths; if you are careful you should be able to\n", 
    "# get a classification accuracy of over 0.35 on the validation set.\n", 
    "from cs231n.classifiers import Softmax\n", 
    "results = {}\n", 
    "best_val = -1\n", 
    "best_softmax = None\n", 
    "learning_rates = [1e-7, 5e-7]\n", 
    "regularization_strengths = [2.5e4, 5e4]\n", 
    "\n", 
    "################################################################################\n", 
    "# TODO:                                                                        #\n", 
    "# Use the validation set to set the learning rate and regularization strength. #\n", 
    "# This should be identical to the validation that you did for the SVM; save    #\n", 
    "# the best trained softmax classifer in best_softmax.                          #\n", 
    "################################################################################\n", 
    "pass\n", 
    "################################################################################\n", 
    "#                              END OF YOUR CODE                                #\n", 
    "################################################################################\n", 
    "    \n", 
    "# Print out results.\n", 
    "for lr, reg in sorted(results):\n", 
    "    train_accuracy, val_accuracy = results[(lr, reg)]\n", 
    "    print('lr %e reg %e train accuracy: %f val accuracy: %f' % (\n", 
    "                lr, reg, train_accuracy, val_accuracy))\n", 
    "    \n", 
    "print('best validation accuracy achieved during cross-validation: %f' % best_val)"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# evaluate on test set\n", 
    "# Evaluate the best softmax on test set\n", 
    "y_test_pred = best_softmax.predict(X_test)\n", 
    "test_accuracy = np.mean(y_test == y_test_pred)\n", 
    "print('softmax on raw pixels final test set accuracy: %f' % (test_accuracy, ))"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }, 
  {
   "execution_count": null, 
   "cell_type": "code", 
   "source": [
    "# Visualize the learned weights for each class\n", 
    "w = best_softmax.W[:-1,:] # strip out the bias\n", 
    "w = w.reshape(32, 32, 3, 10)\n", 
    "\n", 
    "w_min, w_max = np.min(w), np.max(w)\n", 
    "\n", 
    "classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n", 
    "for i in range(10):\n", 
    "    plt.subplot(2, 5, i + 1)\n", 
    "    \n", 
    "    # Rescale the weights to be between 0 and 255\n", 
    "    wimg = 255.0 * (w[:, :, :, i].squeeze() - w_min) / (w_max - w_min)\n", 
    "    plt.imshow(wimg.astype('uint8'))\n", 
    "    plt.axis('off')\n", 
    "    plt.title(classes[i])"
   ], 
   "outputs": [], 
   "metadata": {
    "collapsed": false
   }
  }
 ], 
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2", 
   "name": "python2", 
   "language": "python"
  }, 
  "language_info": {
   "mimetype": "text/x-python", 
   "nbconvert_exporter": "python", 
   "name": "python", 
   "file_extension": ".py", 
   "version": "2.7.6", 
   "pygments_lexer": "ipython2", 
   "codemirror_mode": {
    "version": 2, 
    "name": "ipython"
   }
  }
 }
}